package com.moyz.adi.common.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.moyz.adi.common.base.ThreadContext;
import com.moyz.adi.common.cosntant.AdiConstant;
import com.moyz.adi.common.dto.AskReq;
import com.moyz.adi.common.entity.AiModel;
import com.moyz.adi.common.entity.Conversation;
import com.moyz.adi.common.entity.ConversationMessage;
import com.moyz.adi.common.entity.User;
import com.moyz.adi.common.enums.ChatMessageRoleEnum;
import com.moyz.adi.common.enums.ErrorEnum;
import com.moyz.adi.common.exception.BaseException;
import com.moyz.adi.common.helper.LLMContext;
import com.moyz.adi.common.helper.QuotaHelper;
import com.moyz.adi.common.helper.SSEEmitterHelper;
import com.moyz.adi.common.mapper.ConversationMessageMapper;
import com.moyz.adi.common.util.LocalCache;
import com.moyz.adi.common.util.UuidUtil;
import com.moyz.adi.common.vo.*;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.List;

import static com.moyz.adi.common.enums.ErrorEnum.A_CONVERSATION_NOT_FOUND;
import static com.moyz.adi.common.enums.ErrorEnum.B_MESSAGE_NOT_FOUND;

@Slf4j
@Service
public class ConversationMessageService extends ServiceImpl<ConversationMessageMapper, ConversationMessage> {

    //告诉Spring容器在第一次被请求时才初始化这个bean，而不是在应用程序启动时就立即初始化。
    @Lazy
    @Resource
    private ConversationMessageService self;

    @Resource
    private QuotaHelper quotaHelper;

    @Resource
    private UserDayCostService userDayCostService;

    @Lazy
    @Resource
    private ConversationService conversationService;

    @Resource
    private SSEEmitterHelper sseEmitterHelper;


    public SseEmitter sseAsk(AskReq askReq) {
        // 创建一个新的SseEmitter实例，用于管理与客户端的SSE连接。
        SseEmitter sseEmitter = new SseEmitter();
        // 获取当前用户信息
        User user = ThreadContext.getCurrentUser();
        // 检查或完成SSE连接：如果用户已经有活跃的SSE连接，则立即完成新的SseEmitter（即关闭它），并返回。
        if (!sseEmitterHelper.checkOrComplete(user, sseEmitter)) {
            return sseEmitter;
        }
        // 如果上述检查通过，开始SSE流程，这通常包括设置Redis键值对来追踪用户的请求状态等操作。
        sseEmitterHelper.startSse(user, sseEmitter);
        // 异步检查并根据需要将数据推送给客户端。这可能涉及到监听某些事件、处理业务逻辑等。
        self.asyncCheckAndPushToClient(sseEmitter, ThreadContext.getCurrentUser(), askReq);
        // 返回配置好的SseEmitter给调用者，以便它可以被用于后续的操作或直接返回给HTTP响应。
        return sseEmitter;
    }

    private boolean checkConversation(SseEmitter sseEmitter, User user, AskReq askReq) {
        try {

            //check 1: the conversation has been deleted
            Conversation delConv = conversationService.lambdaQuery()
                    .eq(Conversation::getUuid, askReq.getConversationUuid())
                    .eq(Conversation::getIsDeleted, true)
                    .one();
            if (null != delConv) {
                sseEmitterHelper.sendErrorAndComplete(user.getId(), sseEmitter, "该对话已经删除");
                return false;
            }

            //check 2: conversation quota
            Long convsCount = conversationService.lambdaQuery()
                    .eq(Conversation::getUserId, user.getId())
                    .eq(Conversation::getIsDeleted, false)
                    .count();
            long convsMax = Integer.parseInt(LocalCache.CONFIGS.get(AdiConstant.SysConfigKey.CONVERSATION_MAX_NUM));
            if (convsCount >= convsMax) {
                sseEmitterHelper.sendErrorAndComplete(user.getId(), sseEmitter, "对话数量已经达到上限，当前对话上限为：" + convsMax);
                return false;
            }

            //check 3: current user's quota
            ErrorEnum errorMsg = quotaHelper.checkTextQuota(user);
            if (null != errorMsg) {
                sseEmitterHelper.sendErrorAndComplete(user.getId(), sseEmitter, errorMsg.getInfo());
                return false;
            }
        } catch (Exception e) {
            log.error("error", e);
            sseEmitter.completeWithError(e);
            return false;
        }
        return true;
    }

//Spring框架提供的一个注解，用于简化异步方法的执行。该方法将在单独的线程中异步执行，而不是在调用者的线程中同步执行
    @Async
/**
 * 异步检查业务规则并向客户端推送消息。
 *
 * @param sseEmitter SSE发射器，用于管理与客户端之间的SSE连接。
 * @param user       当前用户的信息。
 * @param askReq     包含请求的具体信息，如对话UUID、模型名称等。
 */
    public void asyncCheckAndPushToClient(SseEmitter sseEmitter, User user, AskReq askReq) {
        // 记录开始处理来自特定用户的异步检查和推送操作的日志信息。
        log.info("asyncCheckAndPushToClient, userId:{}", user.getId());

        // 检查业务规则，确保满足继续处理的条件。
        if (!checkConversation(sseEmitter, user, askReq)) {
            return; // 如果检查失败，则直接返回，不再继续执行后续代码。
        }

        // 从数据库中查询与给定conversationUuid匹配的对话记录。
        Conversation conversation = conversationService.lambdaQuery()
                .eq(Conversation::getUuid, askReq.getConversationUuid())
            // oneOpt()：这是MyBatis-Plus框架提供的一个方法，它执行一个查询并期望返回零个或一个结果。
            // 如果查询返回了多于一条记录，则会抛出异常（通常是 TooManyResultsException）。
            // 这个方法返回的是一个 Optional<T> 对象，而不是直接返回实体对象。
            // Optional 是Java 8引入的一个容器类，代表一个值可能存在也可能不存在的情况，从而避免了显式的 null 检查。
                .oneOpt()
            // Optional 类中的一个方法，用于提供一个默认值，当 Optional 中没有值时（即 Optional.isEmpty()），则返回这个默认值。
            // 在这个例子中，默认值是 null。
                .orElse(null);

        // 如果找不到对应的对话记录，则发送错误信息并完成（关闭）SSE连接。
        if (null == conversation) {
            sseEmitterHelper.sendErrorAndComplete(user.getId(), sseEmitter, A_CONVERSATION_NOT_FOUND.getInfo());
            return;
        }

        // 根据请求中的regenerateQuestionUuid字段决定是否重用现有的问题UUID，否则生成一个新的短UUID。
        String questionUuid = StringUtils.isNotBlank(askReq.getRegenerateQuestionUuid()) ?
                askReq.getRegenerateQuestionUuid() : UuidUtil.createShort();

        // 创建SseAskParams对象，并设置各种参数，包括用户信息、UUID、模型名称、SSE发射器以及可能的重新生成的问题UUID。
        SseAskParams sseAskParams = new SseAskParams();
        sseAskParams.setUser(user);
        sseAskParams.setUuid(questionUuid);
        sseAskParams.setModelName(askReq.getModelName());
        sseAskParams.setSseEmitter(sseEmitter);
        sseAskParams.setRegenerateQuestionUuid(askReq.getRegenerateQuestionUuid());

        // 构建AssistantChatParams，这是用来配置AI助手如何响应的参数。
        AssistantChatParams.AssistantChatParamsBuilder assistantBuilder = AssistantChatParams.builder();

        // 如果对话中有系统消息，则将其添加到助手参数中。
        if (StringUtils.isNotBlank(conversation.getAiSystemMessage())) {
            assistantBuilder.systemMessage(conversation.getAiSystemMessage());
        }

        // 如果启用了上下文理解功能，则将对话UUID作为消息ID传递给助手。
        if (Boolean.TRUE.equals(conversation.getUnderstandContextEnable())) {
            assistantBuilder.messageId(askReq.getConversationUuid());
        }

        // 确定提示文本（prompt），如果有重新生成的问题UUID，则从数据库中获取原始问题的备注作为新的提示。
        String prompt = askReq.getPrompt();
        if (StringUtils.isNotBlank(askReq.getRegenerateQuestionUuid())) {
            prompt = getPromptMsgByQuestionUuid(askReq.getRegenerateQuestionUuid()).getRemark();
        }

        // 将图像URL列表也添加到助手参数中。
        assistantBuilder.userMessage(prompt);
        assistantBuilder.imageUrls(askReq.getImageUrls());

        // 设置构建好的助手参数到sseAskParams中。
        sseAskParams.setAssistantChatParams(assistantBuilder.build());

        // 配置用于生成回复的语言模型（LLM）的属性，例如温度值（temperature），这影响了生成文本的随机性和创造性。
        sseAskParams.setLlmBuilderProperties(
                LLMBuilderProperties.builder()
                        .temperature(conversation.getLlmTemperature())
                        .build()
        );

        // 调用commonProcess方法来处理SSE请求，包括但不限于发送初始事件给客户端。
        // 当AI生成了响应后，调用saveAfterAiResponse方法来保存这次交互的相关元数据和内容。
        sseEmitterHelper.commonProcess(sseAskParams, (response, questionMeta, answerMeta) ->
                self.saveAfterAiResponse(user, askReq, response, questionMeta, answerMeta)
        );
    }

    public List<ConversationMessage> listQuestionsByConvId(long convId, long maxId, int pageSize) {
        LambdaQueryWrapper<ConversationMessage> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ConversationMessage::getConversationId, convId);
        queryWrapper.eq(ConversationMessage::getParentMessageId, 0);
        queryWrapper.lt(ConversationMessage::getId, maxId);
        queryWrapper.eq(ConversationMessage::getIsDeleted, false);
        queryWrapper.last("limit " + pageSize);
        queryWrapper.orderByDesc(ConversationMessage::getId);
        return getBaseMapper().selectList(queryWrapper);
    }

    /**
     * 保存 AI 回应后相关数据到数据库中。
     * <p>
     * 此方法负责处理用户与 AI 系统交互后的数据持久化工作，包括：
     * - 检查或创建会话（Conversation）
     * - 处理重新生成的问题（如果适用）
     * - 保存新的问题消息（如果没有重新生成）
     * - 保存 AI 的回应消息
     * - 计算并记录当天的成本
     *
     * @param user         用户信息
     * @param askReq       请求参数，包含提示、会话 UUID 和是否重新生成问题等信息
     * @param response     AI 系统的回应文本
     * @param questionMeta 提问元数据，包括 UUID、Token 数量等
     * @param answerMeta   回答元数据，包括 UUID、Token 数量等
     */
    @Transactional
    public void saveAfterAiResponse(User user, AskReq askReq, String response, PromptMeta questionMeta, AnswerMeta answerMeta) {

        Conversation conversation;
        String prompt = askReq.getPrompt();
        String convUuid = askReq.getConversationUuid();

        // 查询或创建会话
        conversation = conversationService.lambdaQuery()
                .eq(Conversation::getUuid, convUuid)
                .eq(Conversation::getUserId, user.getId())
                .oneOpt()
                .orElseGet(() -> conversationService.createByFirstMessage(user.getId(), convUuid, prompt));

        // 获取当前使用的 AI 模型
        AiModel aiModel = LLMContext.getAiModel(askReq.getModelName());

        // 检查是否需要重新生成问题
        ConversationMessage promptMsg;
        if (StringUtils.isNotBlank(askReq.getRegenerateQuestionUuid())) {
            // 如果有 regenerateQuestionUuid，则获取对应的消息对象
            promptMsg = getPromptMsgByQuestionUuid(askReq.getRegenerateQuestionUuid());
        } else {
            // 否则，保存新的问题消息
            ConversationMessage question = new ConversationMessage();
            question.setUserId(user.getId());
            question.setUuid(questionMeta.getUuid());
            question.setConversationId(conversation.getId());
            question.setConversationUuid(convUuid);
            question.setMessageRole(ChatMessageRoleEnum.USER.getValue());
            question.setRemark(prompt);
            question.setAiModelId(aiModel.getId());
            question.setTokens(questionMeta.getTokens());
            question.setUnderstandContextMsgPairNum(user.getUnderstandContextMsgPairNum());
            question.setAttachments(String.join(",", askReq.getImageUrls()));

            // 插入新问题消息到数据库
            baseMapper.insert(question);

            // 获取刚插入的问题消息对象
            promptMsg = this.lambdaQuery().eq(ConversationMessage::getUuid, questionMeta.getUuid()).one();
        }

        // 保存 AI 的回应消息
        ConversationMessage aiAnswer = new ConversationMessage();
        aiAnswer.setUserId(user.getId());
        aiAnswer.setUuid(answerMeta.getUuid());
        aiAnswer.setConversationId(conversation.getId());
        aiAnswer.setConversationUuid(convUuid);
        aiAnswer.setMessageRole(ChatMessageRoleEnum.ASSISTANT.getValue());
        aiAnswer.setRemark(response);
        aiAnswer.setTokens(answerMeta.getTokens());
        aiAnswer.setParentMessageId(promptMsg.getId());
        aiAnswer.setAiModelId(aiModel.getId());

        // 插入 AI 回应消息到数据库
        baseMapper.insert(aiAnswer);

        // 计算并记录当天的成本
        calcTodayCost(user, conversation, questionMeta, answerMeta, aiModel.getIsFree());
    }

    private void calcTodayCost(User user, Conversation conversation, PromptMeta questionMeta, AnswerMeta answerMeta, boolean isFreeToken) {

        int todayTokenCost = questionMeta.getTokens() + answerMeta.getTokens();
        try {
            //calculate conversation tokens
            conversationService.lambdaUpdate()
                    .eq(Conversation::getId, conversation.getId())
                    .set(Conversation::getTokens, conversation.getTokens() + todayTokenCost)
                    .update();

            userDayCostService.appendCostToUser(user, todayTokenCost, isFreeToken);
        } catch (Exception e) {
            log.error("calcTodayCost error", e);
        }
    }

    private ConversationMessage getPromptMsgByQuestionUuid(String questionUuid) {
        return this.lambdaQuery().eq(ConversationMessage::getUuid, questionUuid).oneOpt().orElseThrow(() -> new BaseException(B_MESSAGE_NOT_FOUND));
    }

    public boolean softDelete(String uuid) {
        return this.lambdaUpdate()
                .eq(ConversationMessage::getUuid, uuid)
                .eq(ConversationMessage::getUserId, ThreadContext.getCurrentUserId())
                .eq(ConversationMessage::getIsDeleted, false)
                .set(ConversationMessage::getIsDeleted, true)
                .update();
    }

}
